package com.summerbird.mallchat.common.chat.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Pair;
import com.summerbird.mallchat.common.chat.dao.*;
import com.summerbird.mallchat.common.chat.domain.dto.MsgReadInfoDTO;
import com.summerbird.mallchat.common.chat.domain.entity.*;
import com.summerbird.mallchat.common.chat.domain.enums.GroupRoleEnum;
import com.summerbird.mallchat.common.chat.domain.enums.MessageMarkActTypeEnum;
import com.summerbird.mallchat.common.chat.domain.enums.MessageTypeEnum;
import com.summerbird.mallchat.common.chat.domain.vo.request.*;
import com.summerbird.mallchat.common.chat.domain.vo.request.member.MemberReq;
import com.summerbird.mallchat.common.chat.domain.vo.response.ChatMessageReadResp;
import com.summerbird.mallchat.common.chat.domain.vo.response.ChatMessageResp;
import com.summerbird.mallchat.common.chat.service.ChatService;
import com.summerbird.mallchat.common.chat.service.ContactService;
import com.summerbird.mallchat.common.chat.service.adapter.MemberAdapter;
import com.summerbird.mallchat.common.chat.service.adapter.MessageAdapter;
import com.summerbird.mallchat.common.chat.service.cache.RoomCache;
import com.summerbird.mallchat.common.chat.service.cache.RoomGroupCache;
import com.summerbird.mallchat.common.chat.service.helper.ChatMemberHelper;
import com.summerbird.mallchat.common.chat.service.strategy.msg.AbstractMsgHandler;
import com.summerbird.mallchat.common.chat.service.strategy.msg.MsgHandlerFactory;
import com.summerbird.mallchat.common.chat.service.strategy.msg.RecallMsgHandler;
import com.summerbird.mallchat.common.chat.strategy.mark.AbstractMsgMarkStrategy;
import com.summerbird.mallchat.common.chat.strategy.mark.MsgMarkFactory;
import com.summerbird.mallchat.common.common.annotation.RedissonLock;
import com.summerbird.mallchat.common.common.domain.enums.NormalOrNoEnum;
import com.summerbird.mallchat.common.common.domain.vo.req.CursorPageBaseReq;
import com.summerbird.mallchat.common.common.domain.vo.resp.CursorPageBaseResp;
import com.summerbird.mallchat.common.common.event.MessageSendEvent;
import com.summerbird.mallchat.common.common.utils.AssertUtil;
import com.summerbird.mallchat.common.user.dao.UserDao;
import com.summerbird.mallchat.common.user.domain.entity.User;
import com.summerbird.mallchat.common.user.domain.enums.ChatActiveStatusEnum;
import com.summerbird.mallchat.common.user.domain.enums.RoleEnum;
import com.summerbird.mallchat.common.user.service.IRoleService;
import com.summerbird.mallchat.common.user.service.adapter.RoomAdapter;
import com.summerbird.mallchat.common.websocket.domain.vo.response.ChatMemberResp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class ChatServiceImpl implements ChatService {

    @Autowired
    private RoomCache roomCache;
    @Autowired
    private RoomFriendDao roomFriendDao;
    @Autowired
    private RoomGroupDao roomGroupDao;
    @Autowired
    private RoomGroupCache roomGroupCache;
    @Autowired
    private GroupMemberDao groupMemberDao;
    @Autowired
    private MessageMarkDao messageMarkDao;
    @Autowired
    private ContactDao contactDao;
    @Autowired
    private MessageDao messageDao;
    @Autowired
    private UserDao userDao;
    @Autowired
    private IRoleService iRoleService;
    @Autowired
    private ContactService contactService;
    @Autowired
    private RecallMsgHandler recallMsgHandler;
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Override
    public Long sendMsg(ChatMessageReq request, Long uid) {
        check(request, uid);
        AbstractMsgHandler strategy = MsgHandlerFactory.getStrategyNoNull(request.getMsgType());
        Long msgId = strategy.checkAndSaveMsg(request, uid);
        // 发送消息发送事件
        applicationEventPublisher.publishEvent(new MessageSendEvent(this, msgId));
        return msgId;
    }

    private void check(ChatMessageReq request, Long uid) {
        Room room = roomCache.get(request.getRoomId());
        // 全员群聊，则跳过检验
        if(room.isHotRoom()){
            return;
        }
        if(room.isRoomFriend()){
            RoomFriend roomFriend = roomFriendDao.getByRoomId(room.getId());
            AssertUtil.equal(roomFriend.getStatus(), NormalOrNoEnum.NORMAL.getStatus(), "您已被对方拉黑");
            AssertUtil.isTrue(uid.equals(roomFriend.getUid1()) || uid.equals(roomFriend.getUid2()), "您已经被对方拉黑");
        }
        if(room.isRoomGroup()){
            RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
            GroupMember groupMember = groupMemberDao.getMember(roomGroup.getRoomId(), uid);
            AssertUtil.isNotEmpty(groupMember, "您已经被移出该群");
        }
    }

    @Override
    public ChatMessageResp getMsgResp(Message message, Long receiveUid) {
        return CollUtil.getFirst(getMsgRespBatch(Collections.singletonList(message), receiveUid));
    }

    /**
     * 组装前端展示信息
     * @param messages
     * @param receiveUid
     * @return
     */
    public List<ChatMessageResp> getMsgRespBatch(List<Message> messages, Long receiveUid) {
        if (CollectionUtil.isEmpty(messages)) {
            return new ArrayList<>();
        }
        //查询消息标志
        List<MessageMark> msgMark = messageMarkDao.getValidMarkByMsgIdBatch(messages.stream().map(Message::getId).collect(Collectors.toList()));
        return MessageAdapter.buildMsgResp(messages, msgMark, receiveUid);
    }

    @Override
    public ChatMessageResp getMsgResp(Long msgId, Long receiveUid) {
        Message message = messageDao.getById(msgId);
        return getMsgResp(message, receiveUid);
    }

    @Override
    public CursorPageBaseResp<ChatMessageResp> getMsgPage(ChatMessagePageReq request, Long receiveUid) {
        // 用最后一条消息id，来限制被踢出的人能看见的最大一条消息
        Long lastMsgId = getLastMsgId(request.getRoomId(), receiveUid);
        CursorPageBaseResp<Message> cursorPage = messageDao.getCursorPage(request.getRoomId(), request, lastMsgId);
        if(cursorPage.isEmpty()){
            return CursorPageBaseResp.empty();
        }
        return CursorPageBaseResp.init(cursorPage, getMsgRespBatch(cursorPage.getList(), receiveUid));
    }

    @Override
    public void setMsgMark(Long uid, ChatMessageMarkReq request) {
        AbstractMsgMarkStrategy strategy = MsgMarkFactory.getStrategyOrNull(request.getMarkType());
        switch (MessageMarkActTypeEnum.of(request.getActType())){
            case MARK:
                strategy.mark(uid, request.getMsgId());
                break;
            case UN_MARK:
                strategy.unMark(uid, request.getMsgId());
                break;
        }
    }

    @Override
    public void recallMsg(Long uid, ChatMessageBaseReq request) {
        Message message = messageDao.getById(request.getMsgId());
        //校验能不能执行撤回
        checkRecall(uid, message);
        //执行消息撤回事件
        recallMsgHandler.recall(uid, message);
    }

    @Override
    @RedissonLock(key = "#uid")
    public void msgRead(Long uid, ChatMessageMemberReq request) {
        Contact contact = contactDao.get(uid, request.getRoomId());
        if(Objects.isNull(contact)){
            contact = new Contact();
            contact.setReadTime(new Date());
            contact.setUid(uid);
            contact.setRoomId(request.getRoomId());
            contactDao.save(contact);
        } else {
            Contact update = new Contact();
            update.setId(contact.getId());
            update.setReadTime(new Date());
            contactDao.updateById(update);
        }
    }

    @Override
    public CursorPageBaseResp<ChatMessageReadResp> getReadPage(Long uid, ChatMessageReadReq request) {
        Message message = messageDao.getById(request.getMsgId());
        AssertUtil.isNotEmpty(message, "消息id有误");
        AssertUtil.equal(uid, message.getFromUid(), "只能查看自己的消息");
        CursorPageBaseResp<Contact> page = null;
        if(request.getSearchType() == 1){
            page = contactDao.getReadPage(message, request);
        } else if(request.getSearchType() == 2){
            page = contactDao.getUnReadPage(message, request);
        }
        if(Objects.isNull(page) || CollectionUtils.isEmpty(page.getList())){
            return CursorPageBaseResp.empty();
        }
        return CursorPageBaseResp.init(page, RoomAdapter.buildReadResp(page.getList()));
    }

    @Override
    public Collection<MsgReadInfoDTO> getMsgReadInfo(Long uid, ChatMessageReadInfoReq request) {
        List<Message> messages = messageDao.listByIds(request.getMsgIds());
        messages.forEach(message -> {
            AssertUtil.equal(uid, message.getFromUid(), "只能查询自己发送的消息");
        });
        return contactService.getMsgReadInfo(messages).values();
    }

    @Override
    public CursorPageBaseResp<ChatMemberResp> getMemberPage(List<Long> memberUidList, MemberReq request) {
        Pair<ChatActiveStatusEnum, String> cursorPair = ChatMemberHelper.getCursorPair(request.getCursor());
        ChatActiveStatusEnum activeStatusEnum = cursorPair.getKey();
        String timeCursor = cursorPair.getValue();
        // 最终列表
        List<ChatMemberResp> result = new ArrayList<>();
        Boolean isLast = Boolean.FALSE;
        CursorPageBaseResp<User> cursorPage = null;
        // 在线列表
        if(activeStatusEnum == ChatActiveStatusEnum.ONLINE){
            cursorPage = userDao.getCursorPage(memberUidList, new CursorPageBaseReq(request.getPageSize(), timeCursor), ChatActiveStatusEnum.ONLINE);
            result.addAll(MemberAdapter.buildMember(cursorPage.getList()));
            if(cursorPage.getIsLast()){// 如果是最后一页,从离线列表再补点数据
                int leftPageSize = request.getPageSize() - cursorPage.getList().size();
                cursorPage = userDao.getCursorPage(memberUidList, new CursorPageBaseReq(leftPageSize, timeCursor), ChatActiveStatusEnum.OFFLINE);
                result.addAll(MemberAdapter.buildMember(cursorPage.getList()));
            }
        }
        // 离线列表
        else if (activeStatusEnum == ChatActiveStatusEnum.OFFLINE) {
            cursorPage = userDao.getCursorPage(null, new CursorPageBaseReq(request.getPageSize(), timeCursor), ChatActiveStatusEnum.OFFLINE);
            result.addAll(MemberAdapter.buildMember(cursorPage.getList()));//添加离线线列表
        }
        timeCursor = cursorPage.getCursor();
        isLast = cursorPage.getIsLast();
        // 获取群成员角色信息，主要是群主和管理员
        List<Long> uidList = result.stream().map(ChatMemberResp::getUid).collect(Collectors.toList());
        RoomGroup roomGroup = roomGroupDao.getByRoomId(request.getRoomId());
        Map<Long, Integer> uidMapRole = groupMemberDao.getMemberMapRole(roomGroup.getId(), uidList);
        result.forEach(chatMemberResp -> chatMemberResp.setRoleId(uidMapRole.getOrDefault(chatMemberResp.getUid(), GroupRoleEnum.MEMBER.getType())));
        // 组装结果
        return new CursorPageBaseResp<>(ChatMemberHelper.generateCursor(activeStatusEnum, timeCursor), isLast, result);
    }

    private void checkRecall(Long uid, Message message) {
        AssertUtil.isNotEmpty(message, "消息不能为空");
        AssertUtil.notEqual(message.getType(), MessageTypeEnum.RECALL.getType(), "消息无法撤回");

        boolean hasPower = iRoleService.hasPower(uid, RoleEnum.CHAT_MANAGER);
        if(hasPower){
            return;
        }
        // 判断是否是本人发送
        AssertUtil.equal(uid, message.getFromUid(), "抱歉，您没有权限");
        long between = DateUtil.between(message.getCreateTime(), new Date(), DateUnit.MINUTE);
        AssertUtil.isTrue(between < 2, "覆水难收，超过2分钟的消息不能撤回哦~~");
    }

    private Long getLastMsgId(Long roomId, Long receiveUid) {
        Room room = roomCache.get(roomId);
        AssertUtil.isNotEmpty(room, "房间号有误");
        if(room.isHotRoom()){ // 热点房间不用记录lastMsgId
            return null;
        }
        AssertUtil.isNotEmpty(receiveUid, "请先登录");
        Contact contact = contactDao.get(receiveUid, roomId);
        return contact.getLastMsgId();
    }
}
